#ifndef     __PLAY_TASK_H
#define     __PLAY_TASK_H

#include "./de_mux.h"
#include "./video_decoder.h"
#include "./audio_decoder.h"
#include <thread>
#include <QDebug>
#include "../../app.h"
#include "./sdl_window.h"
#include "./sdl_audio.h"
#include "../utils/msg_queue.h"
#include "../utils/sync.h"

class PlayTask {
public:
    PlayTask(const QString &file_path, SDLWindow &win) :
        m_demux(file_path),
        m_video_decoder(m_demux.video_stream(), win.width, win.height),
        m_audio_decoder(m_demux.audio_stream()),
        m_win(win),
        m_sync( m_video_decoder.time_base(), m_audio_decoder.time_base() ),
        m_audio( m_audio_decoder.param() )
    {

    }   

    void play() {
        m_play_flag = true;
        m_sync.update();

        m_demux_task = std::thread(&PlayTask::demux_task, this);

        m_video_task = std::thread(&PlayTask::video_task, this);

        m_audio_task = std::thread(&PlayTask::audio_task, this);

        wait_finish();
    }

    void exit() {
        
        std::unique_lock<std::mutex> lock(m_exit_mutex);
        if (!m_play_flag) {
            return;
        }
        m_play_flag = false;
        
        m_audio_queue.exit();
        m_video_queue.exit();
        m_sync.exit();
    }

    ~PlayTask() {
        exit();
    }

private:
    void wait_finish() {
        m_demux_task.join();
        m_video_task.join();
        m_audio_task.join();
        exit();

        if (m_demux_ptr) {
            std::rethrow_exception(m_demux_ptr);
        }
        if (m_video_ptr) {
            std::rethrow_exception(m_video_ptr);
        }
        if (m_audio_ptr) {
            std::rethrow_exception(m_audio_ptr);
        }
    }

    void audio_task() {
        qDebug() << "audio task start";
        try {
            do_render_audio();
        } catch (...) {
            exit();
            m_audio_ptr = std::current_exception();
        }
    }

    void video_task() {
        qDebug() << "video task start";
        try {
            do_render_video(); 
        } catch (...) {
            exit();
            m_video_ptr = std::current_exception();
        }
    }

    void demux_task() {
        qDebug() << "demux task start";
        try {
            do_demux();
        } catch (...) {
            exit();
            m_demux_ptr = std::current_exception();
        }
    }

    void do_render_audio() {

        for (;;) {
            AudioBuf buf;
            QueueState state = m_audio_queue.pop(buf);
            if (state != QueueState::OK) {
                throw FFmpegException("audio queue exit");
            }
            if (buf.is_empty()) {
                break;
            }
            if (!m_sync.wait_audio(buf.pts)) {
                throw FFmpegException("audio sync exit");
            }
            m_audio.write(buf);
        }
    }

    void do_render_video() {
        
        for (;;) {
            Image img;
            QueueState state = m_video_queue.pop(img);
            if (state != QueueState::OK) {
                throw FFmpegException("video queue exit");
            }
            if (img.is_empty()) {
                break;
            }

            if (!m_sync.wait_video(img.pts)) {
                throw FFmpegException("video sync exit");
            }
          //  m_win.refresh(img);

            SharedImage si = make_shared_image(std::move(img));
            run_on_ui([&win = m_win, si](){
                win.refresh(*si.get());
            });
        }
    }

    void do_demux() {
        Packet packet( av_packet_alloc() );

        for (;;) {
            if (!m_demux.read_packet(packet.packet)) {
                qDebug() << "demux finish"; 
                break;
            }

            if (packet->stream_index == m_demux.video_index()) {
                
                if (!m_video_decoder.send_packet(packet.packet)) {
                    throw FFmpegException("video send packet fail");
                }

                for (;;) {
                    Image img;
                    if (!m_video_decoder.receive_image(img)) {
                        break;
                    }
                    QueueState state = m_video_queue.push( std::move(img) );
                    if (state != QueueState::OK) {
                        throw FFmpegException("video queue exit");
                    }
                }

            } else if (packet->stream_index == m_demux.audio_index()) {
                
                if (!m_audio_decoder.send_packet(packet.packet)) {
                    throw FFmpegException("audio send packet fail");    
                }

                for (;;) {
                    AudioBuf buf;
                    if (!m_audio_decoder.receive_audio_frame(buf)) {
                        break;
                    }
                    QueueState state = m_audio_queue.push( std::move(buf) );
                    if (state != QueueState::OK) {
                        throw FFmpegException("audio queue exit");
                    }
                }
            }

            packet.unref();
        }
        Image img;
        AudioBuf buf;
        m_video_queue.push( std::move(img) );
        m_audio_queue.push( std::move(buf) );
    }

    DeMux m_demux;
    VideoDecoder m_video_decoder;
    AudioDecoder m_audio_decoder;
    SDLWindow &m_win;

    MsgQueue<Image> m_video_queue;
    MsgQueue<AudioBuf> m_audio_queue;

    std::exception_ptr m_demux_ptr;
    std::exception_ptr m_video_ptr;
    std::exception_ptr m_audio_ptr;

    std::thread m_demux_task;
    std::thread m_video_task;
    std::thread m_audio_task;

    AVSync m_sync;
    SDLAudio m_audio;
    UiSync m_ui;

    std::mutex m_exit_mutex;
    bool m_play_flag = false;
};



#endif
